package com.barney.support.image;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.CropImageFilter;
import java.awt.image.FilteredImageSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import javax.imageio.ImageIO;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.util.Assert;

/**
 * Java2D API 기반의 이미지 관리자
 */
public class ImageManager {

	/**
	 * 이미지 파일의 너비와 높이를 반환한다.
	 * 
	 * @param file 이미지 파일
	 */
	public Dimension getDimension(File file) {
		FileInputStream fileInputStream = null;
		try {
			fileInputStream = new FileInputStream(file);
			BufferedImage srcImg = ImageIO.read(fileInputStream);
			return new Dimension(srcImg.getWidth(), srcImg.getHeight());
		} catch (IOException ioe) {
			return null;
		} finally {
			IOUtils.closeQuietly(fileInputStream);
		}
	}

	/**
	 * 이미지 크기를 원본 이미지 비율에 따라 변경한다.
	 * 
	 * @see #resize(File, File, int, int, boolean)
	 */
	public void resize(File original, File target, int targetWidth, int targetHeight) {
		resize(original, target, targetWidth, targetHeight, true);
	}

	/**
	 * 이미지 크기를 변경한다.<br/>
	 * <strong>주의 - 원본 이미지 파일의 너비와 높이가 만들 이미지 파일의 너비와 높이보다 작다면 파일을 그대로 복사한다.</strong>
	 * 
	 * @param original 원본 이미지 파일
	 * @param target 만들 이미지 파일
	 * @param targetWidth 만들 이미지의 너비
	 * @param targetHeight 만들 이미지의 높이
	 * @param keepRatio 값이 <code>true</code>이면 원본 이미지 비율에 따라 이미지를 만든다.
	 */
	public void resize(File original, File target, int targetWidth, int targetHeight,
			boolean keepRatio) {
		Assert.isTrue(targetWidth > 0);
		Assert.isTrue(targetHeight > 0);
		String ext = FilenameUtils.getExtension(target.getName());
		Assert.hasText(ext);

		FileInputStream fileInputStream = null;
		try {
			fileInputStream = new FileInputStream(original);
			BufferedImage srcImg = ImageIO.read(fileInputStream);

			boolean needResize = srcImg.getWidth() > targetWidth
					|| srcImg.getHeight() > targetHeight;

			if (!needResize) {
				FileUtils.copyFile(original, target);
				return;
			}

			resize(srcImg, target, targetWidth, targetHeight, keepRatio);
		} catch (IOException ioe) {
			throw new RuntimeException(ioe.getMessage(), ioe);
		} finally {
			IOUtils.closeQuietly(fileInputStream);
		}
	}

	/**
	 * 이미지 크기를 변경한다.
	 * 
	 * @param srcImg 원본 이미지
	 * @param target 만들 파일
	 * @param targetWidth 만들 이미지의 너비
	 * @param targetHeight 만들 이미지의 높이
	 * @param keepRatio 값이 <code>true</code>이면 원본 이미지 비율에 따라 이미지를 만든다.
	 */
	public void resize(BufferedImage srcImg, File target, int targetWidth, int targetHeight,
			boolean keepRatio) throws IOException {
		Dimension finalWidthHeight = getFinalSize(targetWidth, targetHeight, keepRatio, srcImg);

		Image filteredImage = srcImg;
		boolean needCrop = !keepRatio;
		if (needCrop) {
			filteredImage = crop(srcImg, targetWidth, targetHeight);
		}

		BufferedImage destimg = new BufferedImage(finalWidthHeight.width, finalWidthHeight.height,
				BufferedImage.TYPE_INT_BGR);

		Graphics2D g = destimg.createGraphics();

		g.drawImage(filteredImage, 0, 0, finalWidthHeight.width, finalWidthHeight.height, null);

		String ext = FilenameUtils.getExtension(target.getName());
		ImageIO.write(destimg, ext, target);
	}

	private Dimension getFinalSize(int targetWidth, int targetHeight, boolean keepRatio,
			BufferedImage srcImg) {
		Dimension argSize = new Dimension(targetWidth, targetHeight);
		Dimension finalSize = argSize;

		if (keepRatio) {
			finalSize = getRatioSize(new Dimension(srcImg.getWidth(), srcImg.getHeight()), argSize);
		}
		return finalSize;
	}

	private Dimension getRatioSize(Dimension originalWh, Dimension targetWh) {

		double widthRatio = targetWh.getWidth() / originalWh.getWidth();
		double heightRatio = targetWh.getHeight() / originalWh.getHeight();

		double resizeRatio = Math.min(widthRatio, heightRatio);

		Dimension finalSize = new Dimension((int) (originalWh.getWidth() * resizeRatio),
				(int) (originalWh.getHeight() * resizeRatio));

		return finalSize;
	}

	private Image crop(BufferedImage srcImg, int targetWidth, int targetHeight) {
		Rectangle cropArea = calculateCropRectangle(srcImg.getWidth(), srcImg.getHeight(),
				targetWidth, targetHeight);

		Toolkit toolkit = Toolkit.getDefaultToolkit();
		Image cropImage = toolkit.createImage(new FilteredImageSource(srcImg.getSource(),
				new CropImageFilter(cropArea.x, cropArea.y, cropArea.width, cropArea.height)));

		return cropImage;
	}

	private Rectangle calculateCropRectangle(int originalWidth, int originalHeight,
			int thumbnailWidth, int thumbnailHeight) {
		Dimension size = calculateCropSize(new Dimension(originalWidth, originalHeight),
				new Dimension(thumbnailWidth, thumbnailHeight));
		int x = (size.width == originalWidth) ? 0 : (originalWidth - size.width) / 2;
		int y = (size.height == originalHeight) ? 0 : (originalHeight - size.height) / 2;

		return new Rectangle(x, y, size.width, size.height);
	}

	private Dimension calculateCropSize(Dimension originalSize, Dimension thumbnailSize) {
		double originalAspectRatio = originalSize.getWidth() / originalSize.getHeight();
		double thumbnailAspectRatio = thumbnailSize.getWidth() / thumbnailSize.getHeight();

		if (Double.compare(originalAspectRatio, thumbnailAspectRatio) == 0) {
			return new Dimension(originalSize);
		}

		int targetWidth = originalSize.width;
		int targetHeight = originalSize.height;

		if (thumbnailAspectRatio > originalAspectRatio) {
			targetHeight = (int) (originalSize.getWidth() * (thumbnailSize.getHeight() / thumbnailSize
					.getWidth()));
		} else {
			targetWidth = (int) (originalSize.getHeight() * thumbnailAspectRatio);
		}

		return new Dimension(targetWidth, targetHeight);
	}
}
